#include "TimmerAwait.h"
#include <map>
#include <mutex>

namespace nco
{
    inline int64_t clockms(int64_t ms)
    {
        return clock() / (CLOCKS_PER_SEC / 1000.f) + ms;
    }

    Sleep::Sleep(int64_t ms)
        : m_waitUntil(clockms(ms))
    {
    }

    Sleep::Sleep(const std::chrono::seconds &second)
        : Sleep(second.count() * 1000.f)
    {
    }

    Sleep::Sleep(const std::chrono::milliseconds &ms)
        : Sleep(ms.count())
    {
    }

    bool Sleep::await_ready() const
    {
        return clockms(0) > m_waitUntil;
    }

    void Sleep::await_resume()
    {
    }

    void Sleep::await_suspend(std::coroutine_handle<> handle)
    {
        SleepScheduler::add_Co(m_waitUntil, handle);
    }

    struct AwaitSet
    {
        std::mutex m_mt;
        std::map<clock_t, std::coroutine_handle<>> m_cos;
    };

    AwaitSet gAwaitSet;

    void SleepScheduler::operator()()
    {
        int64_t cur = clock() / (double)CLOCKS_PER_SEC * 1000.f;

        try
        {
            gAwaitSet.m_mt.lock();
            while (!gAwaitSet.m_cos.empty())
            {
                auto beg = gAwaitSet.m_cos.begin();
                if (beg->first <= cur)
                {
                    gAwaitSet.m_cos.erase(beg);
                    gAwaitSet.m_mt.unlock();
                    beg->second.resume();
                    gAwaitSet.m_mt.lock();
                }
                else
                {
                    break;
                }
            }
            gAwaitSet.m_mt.unlock();
        }
        catch (...)
        {
            gAwaitSet.m_mt.unlock();
            throw;
        }
    }

    void SleepScheduler::add_Co(clock_t ct, const std::coroutine_handle<> &handle)
    {
        while (gAwaitSet.m_cos.find(ct) != gAwaitSet.m_cos.end())
        {
            ct++;
        }

        {
            std::lock_guard l{gAwaitSet.m_mt};
            gAwaitSet.m_cos.insert({ct, handle});
        }
    }

} // namespace nco
